package com.huaxonline.boot.web.sign;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.huaxonline.boot.cache.redis.template.RedisUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 签名配置类 - 优化版本
 * @author zhaoshulei
 * @since 2023/9/13
 */
@Slf4j
@SpringBootConfiguration
@ConfigurationProperties("huaxonline.sign")
@ConditionalOnProperty(name = "huaxonline.sign.enabled", havingValue = "true")
@Data
public class SignConfig {
    
    /**
     * 应用凭证列表
     */
    private List<Credential> credentials;
    
    /**
     * API调用允许的时间范围（毫秒），默认15分钟
     */
    private long timestampDisparity = 900000L;
    
    /**
     * 是否验证timestamp字段
     */
    private Boolean isCheckTimestamp = true;
    
    /**
     * 是否验证nonce字段
     */
    private Boolean isCheckNonce = true;
    
    /**
     * 需要验签的路由地址
     */
    private List<String> includePath;
    
    /**
     * 不用验签的路由地址
     */
    private List<String> excludePath;
    
    /**
     * 签名算法类型，支持：SHA256, MD5, HMAC-SHA256
     */
    private String algorithm = "SHA256";
    
    /**
     * 是否启用请求体签名
     */
    private Boolean enableBodySign = true;
    
    /**
     * 是否启用调试模式
     */
    private Boolean debugMode = false;
    
    /**
     * 签名密钥缓存过期时间（秒）
     */
    private Long secretCacheExpire = 3600L;
    
    /**
     * 是否启用IP白名单
     */
    private Boolean enableIpWhitelist = false;
    
    /**
     * IP白名单
     */
    private List<String> ipWhitelist;
    
    /**
     * 是否启用请求频率限制
     */
    private Boolean enableRateLimit = false;
    
    /**
     * 请求频率限制（每分钟请求次数）
     */
    private Integer rateLimitPerMinute = 100;
    
    /**
     * 是否启用签名重放检测
     */
    private Boolean enableReplayDetection = true;
    
    /**
     * 签名重放检测时间窗口（秒）
     */
    private Long replayDetectionWindow = 300L;
    
    /**
     * 是否启用签名时间窗口滑动
     */
    private Boolean enableSlidingWindow = false;
    
    /**
     * 滑动窗口大小（毫秒）
     */
    private Long slidingWindowSize = 60000L;
    
    @Autowired
    private RedisUtil redisUtil;
    
    /**
     * 验证配置
     */
    public void validate() {
        if (CollectionUtil.isEmpty(credentials)) {
            throw new IllegalArgumentException("签名凭证列表不能为空");
        }
        
        for (Credential credential : credentials) {
            if (StrUtil.isBlank(credential.getAppId())) {
                throw new IllegalArgumentException("AppId不能为空");
            }
            if (StrUtil.isBlank(credential.getSecret())) {
                throw new IllegalArgumentException("Secret不能为空");
            }
        }
        
        if (timestampDisparity <= 0) {
            throw new IllegalArgumentException("时间戳容差必须大于0");
        }
        
        if (enableRateLimit && rateLimitPerMinute <= 0) {
            throw new IllegalArgumentException("请求频率限制必须大于0");
        }
    }
    
    /**
     * 获取应用密钥
     */
    public String getSecret(String appId) {
        if (CollectionUtil.isEmpty(credentials)) {
            return null;
        }
        
        return credentials.stream()
                .filter(credential -> appId.equals(credential.getAppId()))
                .map(Credential::getSecret)
                .findFirst()
                .orElse(null);
    }
    
    /**
     * 检查IP是否在白名单中
     */
    public boolean isIpAllowed(String ip) {
        if (!enableIpWhitelist || CollectionUtil.isEmpty(ipWhitelist)) {
            return true;
        }
        return ipWhitelist.contains(ip);
    }
    
    /**
     * 检查请求频率
     */
    public boolean checkRateLimit(String appId, String ip) {
        if (!enableRateLimit) {
            return true;
        }
        
        String key = String.format("sign:rate:%s:%s", appId, ip);
        String count = (String) redisUtil.get(key);
        
        if (count == null) {
            redisUtil.setEx(key, "1", 60L, TimeUnit.SECONDS);
            return true;
        }
        
        int currentCount = Integer.parseInt(count);
        if (currentCount >= rateLimitPerMinute) {
            return false;
        }
        redisUtil.incrBy(key,1);
        return true;
    }
    
    /**
     * 检查签名重放
     */
    public boolean checkReplay(String appId, String signature) {
        if (!enableReplayDetection) {
            return true;
        }
        
        String key = String.format("sign:replay:%s:%s", appId, signature);
        String exists = (String) redisUtil.get(key);
        
        if (exists != null) {
            return false;
        }
        
        redisUtil.setEx(key, "1", replayDetectionWindow, TimeUnit.SECONDS);
        return true;
    }
    
    @Bean
    @ConditionalOnBean(SignConfig.class)
    public SignTemplate signTemplate() {
        // 验证配置
        this.validate();
        
        SignTemplate signTemplate = SignTemplate.getInstance();
        signTemplate.setSignConfig(this);
        signTemplate.setRedisUtil(redisUtil);
        
        // 构建密钥映射
        Map<String, String> apiKey = new ConcurrentHashMap<>();
        this.getCredentials().forEach(item -> {
            apiKey.put(item.getAppId(), item.getSecret());
        });
        signTemplate.setApiKey(apiKey);
        
        log.info("签名功能已启用，算法: {}, 时间容差: {}ms, 启用请求体签名: {}", 
                algorithm, timestampDisparity, enableBodySign);
        
        return signTemplate;
    }
    
    @Configuration
    @ConditionalOnBean(SignConfig.class)
    public class SignWebMvcConfigure implements WebMvcConfigurer {
        
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            InterceptorRegistration registration = registry.addInterceptor(
                    new SignInterceptor(handle -> SignUtil.checkRequest())
            );
            
            if (CollectionUtil.isNotEmpty(includePath)) {
                registration.addPathPatterns(includePath);
            }
            if (CollectionUtil.isNotEmpty(excludePath)) {
                registration.excludePathPatterns(excludePath);
            }
            
            log.info("签名拦截器已注册，包含路径: {}, 排除路径: {}", includePath, excludePath);
        }
    }
    
    @Bean
    @ConditionalOnBean(SignConfig.class)
    public FilterRegistrationBean<SignFilter> signFilter() {
        FilterRegistrationBean<SignFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new SignFilter());
        filterRegistrationBean.setOrder(1); // 设置过滤器优先级
        return filterRegistrationBean;
    }
}